#Loader.py -- loads configuration from keybindings.txt and config.txt
import traceback
import string
import sys, os
import libtcodpy as L
#the paths of the config files
KEYBINDINGS_PATH = "config/keybindings.txt"
CONFIG_PATH = "config/config.txt"

#key commands
#for aligning keybindings on the menu and determining if there is another keybinding

bindstrings = ["Scroll Left","Scroll Right","Scroll Up","Scroll Down","Fast Scroll Left","Fast Scroll Right","Fast Scroll Up","Fast Scroll Down"
,"Begin Draw","Stop Draw","Finish Draw","Move Up Left","Move Left","Move Down Left","Move Down","Move Down Right","Move Right","Move Up Right","Move Up","Wait","Add Door","Add Creature","Pause","Take Screenshot","Toggle Fullscreen","Menu Back","Menu Down","Menu Up","Menu Left","Menu Right","Menu Select"]

#configuration values
config = ["MAP WIDTH","MAP HEIGHT","MAX FPS","TURNS PER FRAME"]

SCREEN_WIDTH = 100#screen width constants
SCREEN_HEIGHT = 60

def StopConsoleErrorScreen(error):#an error that stops and quits the program
	L.console_clear(0)
	L.console_set_background_color(0,L.black)
	L.console_set_foreground_color(0,L.white)
	L.console_print_left_rect(0,0,0,SCREEN_WIDTH,SCREEN_HEIGHT,L.BKGND_NONE,error)
	L.console_flush()
	L.console_wait_for_keypress(True)
	sys.exit(1)

def PauseConsoleErrorScreen(error):#an error that pauses the program before continuing
	L.console_clear(0)
	L.console_set_background_color(0,L.black)
	L.console_set_foreground_color(0,L.white)
	L.console_print_left_rect(0,0,0,SCREEN_WIDTH,SCREEN_HEIGHT,L.BKGND_NONE,error)
	L.console_flush()
	L.console_wait_for_keypress(True)

def removeComments(string):#remove the comments "//" from a configuration file
	data = string
	while (True):#keep going
		try:
			index = data.index("//")#comment
		except ValueError:#no more comments
			return data
		try:
			index2 = data.index("\n",index)#delete to newline
		except ValueError:#if the substring is not found
			data = data[0:index]#remove the rest
			return data#no more comments
		data = data[0:index]+data[index2+1:len(data)]#get rid of the comments

def loadConfigFile(filename):#load the data from a config file , breaking it up by what is in the brackets
	try:
		File = open(filename,"r")#open the file
	except IOError:#could not open file
		print "Error! The essential configuration file "+filename+" could not be opened, the program will now exit!"
		sys.exit(1)
	data = File.read()#read the file
	File.close()#close the file
	data = removeComments(data)#remove comments
	data = string.replace(data, "\n", "")#get rid of newlines
	data = data.split("][")#split the edges of the boxes
	data[0] = data[0].strip("[")#cut the edges of the boxes
	data[len(data)-1] = data[len(data)-1].strip("]")
	return data

def saveConfigFile(filename,data):#save the data from a config file, where data is a bunch of strings that are going to be bounded
	#try to match the box locations ;)
	try:
		File = open(filename,"w")#try to open the file in advance, give an error if impossible
	except IOError:
		PauseConsoleErrorScreen("Warning: Could not modify configuration file "+filename+".")
		return
	savedata = data#put the data in a good format
	for i in range(len(savedata)):
		if (savedata[i][0:2] !="//"):#if no comment
			savedata[i] = "["+savedata[i]+"]\n"#bracket it
	File.writelines(savedata)#write the lines
	File.close()
	

global thekey#the key that you set and get
def setKey(key):
	global thekey
	thekey = key
def Key():
	global thekey
	return thekey
			


#special key bindings, used to print the bindings as a string, and retrieve the data

specialbindstrings = {L.KEY_ESCAPE:"Esc",L.KEY_BACKSPACE:"Backspace",L.KEY_TAB:"Tab",L.KEY_ENTER:"Enter",L.KEY_SHIFT:"Shift",L.KEY_CONTROL:"Ctrl",
L.KEY_ALT:"Alt",L.KEY_PAUSE:"Pause",L.KEY_CAPSLOCK:"Caps Lock",L.KEY_PAGEUP:"Page Up",L.KEY_PAGEDOWN:"Page Down",L.KEY_END:"End",L.KEY_HOME:"Home",
L.KEY_UP:"Up",L.KEY_DOWN:"Down",L.KEY_LEFT:"Left",L.KEY_RIGHT:"Right",L.KEY_PRINTSCREEN:"Print Screen",L.KEY_INSERT:"Insert",L.KEY_DELETE:"Delete",
L.KEY_LWIN:"Lwin",L.KEY_RWIN:"Rwin",L.KEY_APPS:"Apps",L.KEY_KPADD:"KP+",L.KEY_KPSUB:"KP-",L.KEY_KPMUL:"KP*",L.KEY_KPDIV:"KP/",L.KEY_F1:"F1",L.KEY_F2:"F2",
L.KEY_F3:"F3",L.KEY_F4:"F4",L.KEY_F5:"F5",L.KEY_F6:"F6",L.KEY_F7:"F7",L.KEY_F8:"F8",L.KEY_F9:"F9",L.KEY_F10:"F10",L.KEY_F11:"F11",L.KEY_F12:"F12",
L.KEY_NUMLOCK:"Num Lock",L.KEY_SCROLLLOCK:"Scroll Lock",L.KEY_SPACE:"Space",L.KEY_0:"0",L.KEY_1:"1",L.KEY_2:"2",L.KEY_3:"3",L.KEY_4:"4",L.KEY_5:"5",
L.KEY_6:"6",L.KEY_7:"7",L.KEY_8:"8",L.KEY_9:"9",L.KEY_KP0:"Numpad0",L.KEY_KP1:"Numpad1",L.KEY_KP2:"Numpad2",L.KEY_KP3:"Numpad3",L.KEY_KP4:"Numpad4",
L.KEY_KP5:"Numpad5",L.KEY_KP6:"Numpad6",L.KEY_KP7:"Numpad7",L.KEY_KP8:"Numpad8",L.KEY_KP9:"Numpad9"}

#reverse special bindings, used to get the bindings associated with a string
reversespecialbindstrings = dict((v,k) for k, v in specialbindstrings.iteritems())

class keyBind:#a keybind class that stores keybind data, and can tell if a particular combination is being pressed
	#stores all of the special bindings for printing out the key combination
	
	def __init__(self):
		self.containsBind = False
		self.keyisVK = False
		self.shiftPressed = False
		self.ctrlPressed = False
		self.altPressed = False
		self.keyvalue = -1
	def setBindFromKeyPress(self,key):#adds a bind from Libtcod, True means a bind was added
		if (key.vk == L.KEY_NONE):#no keypress
			return False
		elif ((key.vk == L.KEY_SHIFT) or (key.vk == L.KEY_CONTROL) or (key.vk == L.KEY_ALT)):#ctrl, shift, and alt don't count
			return False
		self.containsBind = True
		if (key.vk == L.KEY_CHAR):#character
			self.keyvalue = key.c
			self.keyisVK = False
		else:#special
			self.keyvalue = key.vk
			self.keyisVK = True
			self.shiftPressed = (key.shift == 1)
		self.altPressed = (key.lalt == 1) or (key.ralt == 1)
		self.ctrlPressed = (key.lctrl == 1) or (key.rctrl == 1)
		return True
	def matchesKeyBind(self,key):#does it match the keybind
		if (not self.containsBind):#no bind
			return False
		if (key.vk == L.KEY_NONE):#no key
			return False
		if (self.keyisVK):#vk
			if (key.vk != self.keyvalue):#no match
				return False
			if (self.shiftPressed != (key.shift == 1)):#shift
				return False
		else:#char
			if (key.c != self.keyvalue):#no match
				return False
		
		if (self.altPressed != ((key.lalt == 1) or (key.ralt == 1))):#alt
			return False
		if (self.ctrlPressed != ((key.lctrl == 1) or (key.rctrl == 1))):#ctrl
			return False
		return True#same key
	def bindToString(self):#translates a bind into a string
		if (not self.containsBind):
			return ""
		returnstring = ""
		if (self.ctrlPressed):#ctrl and alt
			returnstring += specialbindstrings[L.KEY_CONTROL] + "+"
		if (self.altPressed):
			returnstring += specialbindstrings[L.KEY_ALT] + "+"
		if (self.shiftPressed and self.keyisVK):#shift only necessary for vk
			returnstring += specialbindstrings[L.KEY_SHIFT] + "+"
		if (self.keyisVK):#add the vk
			returnstring += specialbindstrings.get(self.keyvalue,"???")
		else:
			returnstring += chr(self.keyvalue)
		return returnstring
	def bindFromString(self,string):#make a binding from a string
		#handle the CTRL, ALT, and Shift special characters
		if (string.startswith(specialbindstrings[L.KEY_CONTROL]+"+")):
			self.ctrlPressed = True
			string = string.lstrip(specialbindstrings[L.KEY_CONTROL]+"+")
		if (string.startswith(specialbindstrings[L.KEY_ALT]+"+")):
			self.altPressed = True
			string = string.lstrip(specialbindstrings[L.KEY_ALT]+"+")
		if (string.startswith(specialbindstrings[L.KEY_SHIFT]+"+")):
			self.shiftPressed = True
			string = string.lstrip(specialbindstrings[L.KEY_SHIFT]+"+")
		try:#if there is a key binding available
			self.keyvalue = reversespecialbindstrings[string]
			self.keyisVK = True
		except KeyError:#get the char otherwise
			self.keyvalue = ord(string)
			self.keyisVK = False
		self.containsBind = True

def bindString(string):#returns a keybinding that is a binding based on a string
	b = keyBind()
	b.bindFromString(string)
	return b

class keys:#keybinding holder
	binds = {}
	def __init__(self):
		for string in bindstrings:
			self.binds[string] = []#make empty arrays so we don't have to initialize them
	def matchesCommand(self,key,command):#does it match the command
		if (not self.binds.__contains__(command)):
			print "Error!  Command "+command+" not found!"
			return False
		for binding in self.binds[command]:
			if (binding.matchesKeyBind(key)):
				return True
		return False
	def addBind(self,keybind,command):#add the bind
		if (not command in bindstrings):#if the command isn't there
			print "Warning!  Bind "+command+" not found!"
			return
		self.binds[command].append(keybind)#add the command


global keybindings
keybindings = keys()
def getStringsOfBinding(command):#get all of the strings that are used for the binding
	strings = []
	for key in keybindings.binds[command]:#bind each string
		strings.append(key.bindToString())
	return strings
def setBinding(binding,command,number):#set the binding of a particular string (used from keybindings)
	keybindings.binds[command][number] = binding
def keyCommand(command):#important, sees if a string command can be executed
	return keybindings.matchesCommand(Key(),command)

def setupKeyBinds(data):#set the keybindings with the initial data
	stringcommand = ""#used to store the command for the keybinding
	for element in data:
		if (":" in element):
			dataset = element.split(":")#split by :, organizational tool
		else:	
			dataset = [element]
		for command in dataset:
			if (command == "BIND"):#new binding
				stringcommand = ""
			elif (command[0] == "\"" and command[len(command)-1] == "\""):#bounded by quotes -- something else
				command = command.strip("\"")
				if (stringcommand == ""):#new string
					stringcommand = command
				else:#New keybinding
					try:
						keybindings.addBind(bindString(command),stringcommand)
					except Exception:
						traceback.print_exc()
						print "Error, could not add keybinding for "+command
						sys.exit(1)
			else:
				print "Error, keybindings file has an unrecognized command"
				sys.exit(1)

def saveKeyBinds():#save the keybindings
	data = []
	data.append("//keybindings, the first string designates the binding, the next the command, and the others the string representations of the characters that can achieve the command\n")#general comment
	for bind in bindstrings:#for each binding
		datamember = "BIND:"+"\""+bind+"\""#initialize the binding
		for keybinds in keybindings.binds[bind]:#for each bind itself
			datamember += ":"+"\""+keybinds.bindToString()+"\""#add the binding's string
		data.append(datamember)
	return data
		

global configvalues
configvalues = {}
for value in config:#set up config
	configvalues[value] = None
def getConfig(value):#get the configuration value
	try:
		return configvalues[value]
	except KeyError:
		print "Configuration value " + value + " is not defined!"
def setupConfig(data):#set the config up with the initial data
	for element in data:
		if (element.count(":") == 1):
			dataset = element.split(":")
			global configvalues
			if dataset[0] not in configvalues:
				print "Error, configuration value "+dataset[0]+" not found!"
			else:
				#try a value conversion to integer
				try:
					dataset[1] = int(dataset[1])
				except ValueError:
					pass
				configvalues[dataset[0]] = dataset[1]#store the value
		else:
			print "Error, configuration file is in an incorrect format"
			sys.exit(1)



#add keybindings -- runs automatically
setupKeyBinds(loadConfigFile(KEYBINDINGS_PATH))
setupConfig(loadConfigFile(CONFIG_PATH))
